Repository navigation
feat: add PHP framework integration primitives - #282
Conversation
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 34e0710: 3 Should fix.
This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
posthog-php-fork_curl Compliance ReportDate: 2026-10-29T16:11:15.765301+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 34ms |
| Format Validation.Event Has Uuid | ✅ | 527ms |
| Format Validation.Event Has Lib Properties | ✅ | 529ms |
| Format Validation.Distinct Id Is String | ✅ | 529ms |
| Format Validation.Token Is Present | ✅ | 529ms |
| Format Validation.Custom Properties Preserved | ✅ | 527ms |
| Format Validation.Event Has Timestamp | ✅ | 529ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 529ms |
| Retry Behavior.Retries On 503 | ✅ | 5850ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2535ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2531ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6345ms |
| Retry Behavior.Implements Backoff | ✅ | 16273ms |
| Retry Behavior.Retries On 500 | ✅ | 5647ms |
| Retry Behavior.Retries On 502 | ✅ | 5647ms |
| Retry Behavior.Retries On 504 | ✅ | 5645ms |
| Retry Behavior.Max Retries Respected | ✅ | 16274ms |
| Deduplication.Generates Unique Uuids | ✅ | 536ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5645ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10859ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5644ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 535ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 530ms |
| Compression.Sends Gzip When Enabled | ✅ | 531ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 529ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 517ms |
| Batch Format.Multiple Events Batched Together | ✅ | 518ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2533ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2531ms |
| Error Handling.Retries On 408 | ✅ | 5646ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 809ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 522ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 520ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 522ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 521ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 521ms |
| Request Payload.Groups Round Trip | ✅ | 521ms |
| Request Payload.Groups Default To Empty Object | ✅ | 520ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 521ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 521ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 521ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 516ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 517ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 523ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 523ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 624ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 622ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 533ms |
posthog-php-lib_curl Compliance ReportDate: 2026-10-29T16:11:15.078340+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 26ms |
| Format Validation.Event Has Uuid | ✅ | 521ms |
| Format Validation.Event Has Lib Properties | ✅ | 522ms |
| Format Validation.Distinct Id Is String | ✅ | 523ms |
| Format Validation.Token Is Present | ✅ | 522ms |
| Format Validation.Custom Properties Preserved | ✅ | 522ms |
| Format Validation.Event Has Timestamp | ✅ | 522ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 522ms |
| Retry Behavior.Retries On 503 | ✅ | 5832ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2524ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2525ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6332ms |
| Retry Behavior.Implements Backoff | ✅ | 16245ms |
| Retry Behavior.Retries On 500 | ✅ | 5633ms |
| Retry Behavior.Retries On 502 | ✅ | 5631ms |
| Retry Behavior.Retries On 504 | ✅ | 5630ms |
| Retry Behavior.Max Retries Respected | ✅ | 17045ms |
| Deduplication.Generates Unique Uuids | ✅ | 234ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5630ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10839ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5634ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 529ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 524ms |
| Compression.Sends Gzip When Enabled | ✅ | 524ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 525ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 519ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2525ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2525ms |
| Error Handling.Retries On 408 | ✅ | 5631ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 803ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 524ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 521ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 521ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 521ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 521ms |
| Request Payload.Groups Round Trip | ✅ | 521ms |
| Request Payload.Groups Default To Empty Object | ✅ | 523ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 521ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 521ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 521ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 517ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 508ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 525ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 523ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 625ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 623ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 525ms |
posthog-php-socket Compliance ReportDate: 2026-10-29T16:11:08.584767+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 28ms |
| Format Validation.Event Has Uuid | ✅ | 523ms |
| Format Validation.Event Has Lib Properties | ✅ | 524ms |
| Format Validation.Distinct Id Is String | ✅ | 524ms |
| Format Validation.Token Is Present | ✅ | 524ms |
| Format Validation.Custom Properties Preserved | ✅ | 524ms |
| Format Validation.Event Has Timestamp | ✅ | 525ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 524ms |
| Retry Behavior.Retries On 503 | ✅ | 5833ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2527ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2527ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6332ms |
| Retry Behavior.Implements Backoff | ✅ | 16247ms |
| Retry Behavior.Retries On 500 | ✅ | 5632ms |
| Retry Behavior.Retries On 502 | ✅ | 5631ms |
| Retry Behavior.Retries On 504 | ✅ | 5633ms |
| Retry Behavior.Max Retries Respected | ✅ | 16249ms |
| Deduplication.Generates Unique Uuids | ✅ | 532ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5631ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10840ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5632ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 532ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 526ms |
| Compression.Sends Gzip When Enabled | ✅ | 524ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 524ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 520ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2527ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2527ms |
| Error Handling.Retries On 408 | ✅ | 5630ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 802ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 526ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 521ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 522ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 522ms |
| Request Payload.Groups Round Trip | ✅ | 524ms |
| Request Payload.Groups Default To Empty Object | ✅ | 522ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 522ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 524ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 522ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 518ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 508ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 526ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 523ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 626ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 624ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 525ms |
Remove library_name, library_version and release_id from the public client options. Framework integrations override internal Client methods instead, so customers keep POSTHOG_RELEASE_ID as the only documented release id input.
turnipdabeets
left a comment
There was a problem hiding this comment.
LGTM. The primitives map cleanly onto Node's enterContext / getLibraryId() and every one has a consumer in #283. A few non-blocking notes inline.
| * | ||
| * @internal Overridden by PostHog framework integrations; not part of the customer API. | ||
| */ | ||
| protected function libraryName(): string |
There was a problem hiding this comment.
[question] Once #283 reports $lib = posthog-laravel, the server won't recognise it: the usage report php_events bucket, the flags Library enum and the SDK health list only know posthog-php. Do we want an alias PR in posthog (like PostHog/posthog#93911 for rails/aspnetcore) before the Laravel package ships?
There was a problem hiding this comment.
Yes—a separate server registration/alias PR is needed before the Laravel package ships. Rechecked current PostHog/posthog master: posthog-laravel is absent from PHP usage attribution, the Python/Rust flag library registries/parser, and SDK-health frontend/backend registries. This is an attribution/classification gap, not ingestion rejecting events with the new $lib. I recorded it explicitly as a pre-release prerequisite in #282–#287, referencing #93911. SDK-health/version handling must use Laravel's independent 0.1.x package version, not compare it against PHP core 4.x. It does not block the standalone core release, whose default identity remains posthog-php. No backend PR was opened in this SDK follow-up; that separate work remains required before Laravel release.
Additive public API (maintainer-approved):
Client::captureException()and the static facade acceptoptions['source'], emitted as event-level$exception_source. It is independent of semanticmechanism.typeandhandled; it never sets nestedmechanism.source. Invalid/missing typed source is omitted, and generic additional/ambient property bags cannot supply provenance.💡 Motivation and Context
Framework integrations need to keep context active across before/after callbacks, report failed boundaries accurately, and use a client instance without changing the global default.
This is the first PR in a Laravel integration stack. It adds reusable PHP SDK support only:
Client::groupIdentify()and delegate the static facade to it.enterContext()and an idempotent closeable scope. Scopes preserve client and Fiber isolation, including cleanup from another execution path.@internalprotectedClient::libraryName(),libraryVersion()andreleaseId()overrides for PostHog framework integrations (library metadata, and release IDs from cached application configuration). These are not constructor options, so customers keepPOSTHOG_RELEASE_IDas the only documented release ID input. Event properties still take precedence.Public API changes are recorded in the API snapshot. Capture, group-identify, exception metadata and tracing-header specs apply. The optional framework boundary metadata extends the manual exception API without changing its default handled behavior.
💚 How did you test it?
git diff --checkpass.34e07100d9ce06f2c56312a9d1072a59574ffed8againstorigin/mainreported no actionable findings.Review follow-up
8773c43addresses all three bot findings with failing-before/passing-after regressions: configured batch User-Agents (including safe shell/header handling), abandoned Fiber cleanup without retention or object-ID identity reuse, and independently validated exception metadata overrides. The 16 adapter tests also pass. These fixes follow the HTTP client and exception-event-metadata contracts; unrelated Retry-After compliance report failures remain outside this framework PR.📝 Checklist
If releasing new changes
pnpm changeto generate a change intent file.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi using local file tools, PHP/Composer, Git and GitHub CLI. A separate isolated Pi review checked the final core diff. The user requested an independently released Laravel package depending on the PHP SDK, with stacked PRs and an example application. Framework lifecycle and payload support live in the core so the package does not duplicate them. Human review is required before merge.
Stack
Merge bottom-up:
Laravel source lives in this repository but its Composer package is released separately. Publish core ^4.20 first. The distribution repository, protected release environment and Packagist setup remain administrator prerequisites. No release has been published.
Follow-up
ecbc8ccsupplies the internal context-bound snapshot host needed by #283's lazy-exposure feedback. Four regressions failed before the fix (later-session leakage and unscoped snapshots inheriting later context), then passed; the combined flag/framework suites pass 491 tests / 1,899 assertions. A fresh read-only review of this exact commit found no actionable issues. API shape is unchanged. PHP 8.2–8.5, coverage, style, API, and security CI checks pass.Human review follow-up
858c204false, without enqueueing a group event. Static-facade type/key validation keeps its original exceptions/messages. Six real SDK throw reproductions are now green.mechanism=middleware,source=laravel.middleware,handled=false. No derivation between these independent fields or guessing from libraries/stack frames. Eleven source regressions failed before (ignored typed metadata/property-bag spoofing), then passed, including normal static forwarding, invalid override omission, context/session preservation andbefore_sendprovenance.Before the first Laravel release: server SDK registration
posthog-laravelis not yet registered on PostHog server master for PHP usage attribution, Python/Rust feature-flag library classification/parser, or SDK health frontend/backend registries. A separate backend registration/alias PR must land before the Laravel package ships, following the approach in PostHog/posthog#93911. Event ingestion itself is not rejected just because this library identifier is unclassified. SDK health/version handling must respect Laravel's independent package version, not compare0.1.xagainst PHP core4.x. No backend PR, repository or release was created by this SDK follow-up; this remains an explicit pre-release prerequisite alongside core ^4.20 publication and distribution/Packagist setup.